#ifndef __LINALG_VECTOR_H__
#define __LINALG_VECTOR_H__

#include "config.h"
#include "numeric.h"

#include <functional>
#include <initializer_list>

#include <iostream>

LINALG_BEGIN

/* class Vector
 * A vector class.
 */
template<typename T>
class Vector
{
public:
    // Type definitions
    typedef T  value_type;
    typedef T* pointer;
    typedef T& reference;
    typedef const T* const_pointer;
    typedef const T& const_reference;
    typedef size_t size_type;

    // Constructors
    Vector() : m_size(0), m_data(nullptr) {};
    ~Vector()
    {
        delete[] m_data;
    };

    Vector(const std::initializer_list<T>& ilist)
    {
        allocate(ilist.size());
        size_type i = 0;
        for (const auto& item : ilist)
        {
            m_data[i++] = item;
        }
    }

    Vector(size_type size) { allocate(size); }
    Vector(size_type size, const_reference value) : m_size(size)
    {
        allocate(size);
        fill(value);
    }

    Vector(const Vector& other) : m_size(other.m_size)
    {
        allocate(m_size);
        copy_from(other);
    }

    Vector(Vector&& other) noexcept : Vector()
    {
        swap(other);
    }

    Vector& operator= (const Vector& other)
    {
        if (this != &other)
        {
            copy_from(other);
        }
        return *this;
    }

private:
    // Data members
    size_type m_size;
    pointer m_data;

    void allocate(size_type size)
    {
        m_size = size;
        m_data = new value_type[size];
    }

    void destroy()
    {
        delete[] m_data;
        m_data = nullptr;
    }

public:
    // Operators
    reference operator[] (size_type index) { return m_data[index]; }
    const_reference operator[] (size_type index) const { return m_data[index]; }

    /*
     * Auto convert to the first element of the vector (as a scalar).
     */
    operator value_type () const { return m_data[0]; }

    /*
     * Print the vector to the given stream.
     */
    friend std::ostream& operator<< (std::ostream& os, const Vector& v)
    {
        for (size_type i = 0; i < v.m_size; i++)
        {
            os << v.m_data[i] << " ";
        }
        return os;
    }

public:

    // Methods

    /*
     * Convert a scalar to a vector.
     */
    Vector& from_value(const_reference _val)
    {
        resize(1);
        m_data[0] = _val;
        return *this;
    }

    /*
     * Get size of the vector.
     */
    size_type size() const { return m_size; }

    /*
     * Resize the vector to the given size.
     */
    void resize(size_type size)
    {
        if(size == m_size) return;

        pointer new_data = new value_type[size];

        const size_type copy_size = std::min(size, m_size);
        
        // Copy the old data to the new data
        if (m_data != nullptr)
        {
            for(size_t i=0; i<copy_size; i++) {
                new_data[i] = m_data[i];
            }
            delete[] m_data;
        }
        
        m_size = size;
        m_data = new_data;
    }

    /*
     * Fill the vector with the given value.
     */
    void fill(const_reference value)
    {
        for (size_type i = 0; i < m_size; i++)
        {
            m_data[i] = value;
        }
    }

    /*
     * Copy the given data to the vector.
     */
    void copy_from(const_pointer data, size_type sz)
    {
        if(sz > m_size) { resize(sz); }
        for (size_t i = 0; i < sz; i++)
        {
            m_data[i] = data[i];
        }
    }

    /*
     * Copy the given vector to the vector.
     */
    void copy_from(const Vector& other)
    {
        copy_from(other.m_data, other.m_size);
    }

    /*
     * Apply the given function to each element of the vector.
     */
    void apply1(std::function<T(T)>& f)
    {
        for (size_type i = 0; i < m_size; i++)
        {
            m_data[i] = f(m_data[i]);
        }
    }

    void apply1(std::function<T(T)>& f) const
    {
        for (size_type i = 0; i < m_size; i++)
        {
            f(m_data[i]);
        }
    }

    void swap(Vector& other)
    {
        std::swap(m_data, other.m_data);
        std::swap(m_size, other.m_size);
    }


    /*
     * Get the begin iterator.
     */
    pointer begin() { return m_data; }
    const_pointer begin() const { return m_data; }


    /*
     * Get the end iterator.
     */
    pointer end() { return m_data + m_size; }
    const_pointer end() const { return m_data + m_size; }

    /*
     * Get the first element of the vector.
     */
    reference front() { return m_data[0]; }
    const_reference front() const { return m_data[0]; }

    /*
     * Get the last element of the vector.
     */
    reference back() { return m_data[m_size - 1]; }
    const_reference back() const { return m_data[m_size - 1]; }


    /*
     * Get the data of the vector.
     */
    pointer data() { return m_data; }
    const_pointer data() const { return m_data; }


};



template<typename T>
class Numeric<Vector<T> > : public Numeric<T>
{
public:
    static Vector<T> add(const Vector<T>& v1, const Vector<T>& v2)
    {
        using namespace std;
        cout << "BP 2" << endl;
        Vector<T> result(v1.size());
        for (size_t i = 0; i < v1.size(); i++)
        {
            result[i] = Numeric<T>::add(v1[i], v2[i]);
        }
        return std::move(result);
    }

    static Vector<T> sub(const Vector<T>& v1, const Vector<T>& v2)
    {
        Vector<T> result(v1.size());
        for (size_t i = 0; i < v1.size(); i++)
        {
            result[i] = Numeric<T>::sub(v1[i], v2[i]);
        }
        return std::move(result);
    }

    static Vector<T> mul(const Vector<T>& v1, const Vector<T>& v2)
    {
        Vector<T> result(v1.size());
        for (size_t i = 0; i < v1.size(); i++)
        {
            result[i] = Numeric<T>::mul(v1[i], v2[i]);
        }
        return std::move(result);
    }

    static Vector<T> div(const Vector<T>& v1, const Vector<T>& v2)
    {
        Vector<T> result(v1.size());
        for (size_t i = 0; i < v1.size(); i++)
        {
            result[i] = Numeric<T>::div(v1[i], v2[i]);
        }
        return std::move(result);
    }

    static Vector<T> neg(const Vector<T>& v)
    {
        Vector<T> result(v.size());
        for (size_t i = 0; i < v.size(); i++)
        {
            result[i] = Numeric<T>::neg(v[i]);
        }
        return std::move(result);
    }

    static Vector<T> abs(const Vector<T>& v)
    {
        Vector<T> result(v.size());
        for (size_t i = 0; i < v.size(); i++)
        {
            result[i] = Numeric<T>::abs(v[i]);
        }
        return std::move(result);
    }

};


template<typename T>
Vector<T> operator+ (const Vector<T>& v1, const Vector<T>& v2) { return std::move(Numeric<Vector<T>>::add(v1, v2)); }

template<typename T>
Vector<T> operator- (const Vector<T>& v1, const Vector<T>& v2) { return std::move(Numeric<Vector<T>>::sub(v1, v2)); }

template<typename T>
Vector<T> operator* (const Vector<T>& v1, const Vector<T>& v2) { return std::move(Numeric<Vector<T>>::mul(v1, v2)); }

template<typename T>
Vector<T> operator/ (const Vector<T>& v1, const Vector<T>& v2) { return std::move(Numeric<Vector<T>>::div(v1, v2)); }

template<typename T>
Vector<T> operator+ (const Vector<T>& v1, const T& a)
{
    typedef typename Vector<T>::size_type size_type;
    Vector<T> v2(v1.size());
    for(size_type i=0; i<v1.size(); ++i)
    {
        v2[i] = Numeric<T>::add(v1[i], a);
    }

    return std::move(v2);
}

template<typename T>
Vector<T> operator+ (const T& a, const Vector<T>& v1)
{
    typedef typename Vector<T>::size_type size_type;
    Vector<T> v2(v1.size());
    for(size_type i=0; i<v1.size(); ++i)
    {
        v2[i] = Numeric<T>::add(a, v1[i]);
    }

    return std::move(v2);
}

template<typename T>
Vector<T> operator- (const Vector<T>& v1, const T& a)
{
    typedef typename Vector<T>::size_type size_type;
    Vector<T> v2(v1.size());
    for(size_type i=0; i<v1.size(); ++i)
    {
        v2[i] = Numeric<T>::sub(v1[i], a);
    }

    return std::move(v2);
}

template<typename T>
Vector<T> operator- (const T& a, const Vector<T>& v1)
{
    typedef typename Vector<T>::size_type size_type;
    Vector<T> v2(v1.size());
    for(size_type i=0; i<v1.size(); ++i)
    {
        v2[i] = Numeric<T>::sub(a, v1[i]);
    }

    return std::move(v2);
}

template<typename T>
Vector<T> operator* (const Vector<T>& v1, const T& a)
{
    typedef typename Vector<T>::size_type size_type;
    Vector<T> v2(v1.size());
    for(size_type i=0; i<v1.size(); ++i)
    {
        v2[i] = Numeric<T>::mul(v1[i], a);
    }

    return std::move(v2);
}

template<typename T>
Vector<T> operator* (const T& a, const Vector<T>& v1)
{
    typedef typename Vector<T>::size_type size_type;
    Vector<T> v2(v1.size());
    for(size_type i=0; i<v1.size(); ++i)
    {
        v2[i] = Numeric<T>::mul(a, v1[i]);
    }

    return std::move(v2);
}

template<typename T>
Vector<T> operator/ (const Vector<T>& v1, const T& a)
{
    typedef typename Vector<T>::size_type size_type;
    Vector<T> v2(v1.size());
    for(size_type i=0; i<v1.size(); ++i)
    {
        v2[i] = Numeric<T>::div(v1[i], a);
    }

    return std::move(v2);
}

template<typename T>
Vector<T> operator/ (const T& a, const Vector<T>& v1)
{
    typedef typename Vector<T>::size_type size_type;
    Vector<T> v2(v1.size());
    for(size_type i=0; i<v1.size(); ++i)
    {
        v2[i] = Numeric<T>::div(a, v1[i]);
    }

    return std::move(v2);
}

template<typename T>
T sum(const Vector<T>& v)
{
    T result = Numeric<T>::fromInteger(0);
    for (size_t i = 0; i < v.size(); i++)
    {
        result = Numeric<T>::add(result, v[i]);
    }
    return std::move(result);
}

template<typename T>
T dot(const Vector<T>& v1, const Vector<T>& v2) { return sum(Numeric<Vector<T>>::mul(v1, v2)); }

LINALG_END

#endif